package com.company.nuwa.redis.wrapper;

import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.lang.Nullable;

import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>redis set operations wrapper</p >
 *
 * @author Nikola Tesla
 * @version 1.0
 * @date 2020/12/1 10:26
 */
@Slf4j
public class RedisSetOperationsWrapper {

    private final SetOperations<String, String> setOperations;

    public RedisSetOperationsWrapper(StringRedisTemplate stringRedisTemplate) {
        this.setOperations = stringRedisTemplate.opsForSet();
    }

    /**
     * Add given {@code values} to set at {@code key}.
     *
     * @param key    must not be {@literal null}.
     * @param values
     * @return {@literal null} when used in pipeline / transaction.
     * @see <a href="https://redis.io/commands/sadd">Redis Documentation: SADD</a>
     */
    @Nullable
    public <T> Long add(String key, Collection<T> values) {
        String[] strings = values.stream()
                .map(ValueConverter::getString)
                .toArray(String[]::new);
        return setOperations.add(key, strings);
    }

    /**
     * Add given {@code values} to set at {@code key}.
     *
     * @param key must not be {@literal null}.
     * @return {@literal null} when used in pipeline / transaction.
     * @see <a href="https://redis.io/commands/sadd">Redis Documentation: SADD</a>
     */
    @Nullable
    public <T> Long add(String key, T value) {
        return setOperations.add(key, ValueConverter.getString(value));
    }

    /**
     * Get {@code count} distinct random elements from set at {@code key}.
     *
     * @param key       must not be {@literal null}.
     * @param count     nr of members to return
     * @param classType class type
     * @return empty {@link Set} if {@code key} does not exist.
     * @throws IllegalArgumentException if count is negative.
     * @see <a href="https://redis.io/commands/srandmember">Redis Documentation: SRANDMEMBER</a>
     */
    public <T> Set<T> distinctRandomMembers(String key, long count, Class<T> classType) {
        Set<String> set = setOperations.distinctRandomMembers(key, count);
        if (Objects.isNull(set)) {
            return Sets.newHashSet();
        }
        return set.stream().map(val -> ValueConverter.getBean(val, classType)).collect(Collectors.toSet());
    }

    /**
     * Get all elements of set at {@code key}.
     *
     * @param key must not be {@literal null}.
     * @return {@literal null} when used in pipeline / transaction.
     * @see <a href="https://redis.io/commands/smembers">Redis Documentation: SMEMBERS</a>
     */
    public <T> Set<T> members(String key, Class<T> classType) {
        Set<String> set = setOperations.members(key);
        if (Objects.isNull(set)) {
            return Sets.newHashSet();
        }
        return set.stream().map(val -> ValueConverter.getBean(val, classType)).collect(Collectors.toSet());
    }

    /**
     * Check if set at {@code key} contains {@code value}.
     *
     * @param key must not be {@literal null}.
     * @param o
     * @return {@literal null} when used in pipeline / transaction.
     * @see <a href="https://redis.io/commands/sismember">Redis Documentation: SISMEMBER</a>
     */
    public <T> Boolean isMember(String key, T o) {
        String value = ValueConverter.getString(o);
        Boolean flag = setOperations.isMember(key, value);
        return Objects.nonNull(flag) ? flag : false;
    }


    /**
     * Remove given {@code values} from set at {@code key} and return the number of removed elements.
     *
     * @param key    must not be {@literal null}.
     * @param values
     * @return {@literal null} when used in pipeline / transaction.
     * @see <a href="https://redis.io/commands/srem">Redis Documentation: SREM</a>
     */
    public <T> Long remove(String key, T... values) {
        return setOperations.remove(key, values);
    }

}
